/* 
 * File:   main.cpp
 * Authors: Gustavo Cavalcanti, Hadley Magno e Lucas Soares
 *
 * Created on November 30, 2009, 9:33 PM
 */

#include <cstdlib>
#include <iostream>
#include "systemc.h"
using namespace std;
/*
 * 
 */


//ISA da arquitetura
//
//add a, b, c       0-codop 0-a 0-b 0-c 0000-immd
//sub a, b, c       
//ld a, immd(b)     
//st a, immd(b)     
//beq a, b, immd    
//bgt a, b, immd
//jmp immd
//mv a, immd
//


#define NUM_RESV_ST 20

struct ReservationStation {
    int op;
    int qj;
    int qk;
    int vk;
    int vj;
    bool busy;
    int A;
};

struct RegisterStatus {
    int status[16];
};

struct RegisterFile {
    int reg[16];
};

struct InstructionQueue {
    int queue[20];
};

struct Memory {
    int mem[8120];
};

int pc = 0;
int resultados[NUM_RESV_ST];

ReservationStation reservation_station[NUM_RESV_ST];
RegisterStatus register_status;
RegisterFile register_file;
InstructionQueue instruction_queue;
Memory memoria_dados;
Memory memoria_inst;

void initReservationStation() {
    for (int i = 0; i < NUM_RESV_ST; ++i) {
        reservation_station[i].op = 0;
        reservation_station[i].qj = -1;
        reservation_station[i].qk = -1;
        reservation_station[i].vj = 0;
        reservation_station[i].vk = 0;
        reservation_station[i].busy = false;
        reservation_station[i].A = 0;
    }
}

void initRegisterStatus() {
    for (int i = 0; i < 16; ++i) {
        register_status.status[i] = -1;
    }
}

int getOP(int instruction) {
    return (instruction & 0xF0000000) >> 28;
}

int getRD(int instruction) {
    return (instruction & 0x0F000000) >> 24;
}

int getRS(int instruction) {
    return (instruction & 0x00F00000) >> 20;
}

int getRT(int instruction) {
    return (instruction & 0x000F0000) >> 16;
}

int getIMMD(int instruction) {
    return (instruction & 0x0000FFFF);
}

int doOperation(int i) {
    int b = reservation_station[i].vj;
    int c = reservation_station[i].vk;

    switch (reservation_station[i].op) {
        case 0:
            return b + c;
        case 1:
            return b - c;
        case 2:
            return memoria_dados.mem[b + c];
        case 3:
        case 4:
        case 5:
        case 6:
            return 0;
        case 7:
            return c;
        default:
            return 0;
    }
}

void insertInstructionInQueue(int instruction) {
    for (int i = 0; i < 19; ++i) {
        instruction_queue.queue[i] = instruction_queue.queue[i + 1];
    }

    instruction_queue.queue[19] = instruction;
}

void initIQ() {
    for (int i = 0; i < 20; ++i) {
        instruction_queue.queue[i] = -1;
    }
}

int getInstructionFromQueue() {
    return instruction_queue.queue[0];
}

void initMem() {
    FILE * memoria = fopen("memoria.txt", "r");
    FILE * trace = fopen("trace.txt", "w");
    int tmp = 0;

    if (memoria == NULL) {
        printf("Erro ao abrir o arquivo memoria.txt");
        return;
    }

    if (trace == NULL) {
        printf("Erro ao abrir o arquivo trace.txt");
        return;
    }

    for (int i = 0; i < 8120 && !feof(memoria); ++i) {
        fscanf(memoria, "%x", &tmp);
        memoria_inst.mem[i] = tmp;
        fprintf(trace, "%x", memoria_inst.mem[i]);
    }

    fclose(memoria);
    fclose(trace);
}

void fetch() {
    insertInstructionInQueue(memoria_inst.mem[pc]);
    printf("mem: %x\n", memoria_inst.mem[pc]);
    if (pc == 8192)
        pc = 0;
    else
        ++pc;
}

void issue() {
    int instruction = getInstructionFromQueue();
    int op = getOP(instruction);
    int rd = getRD(instruction);
    int rs = getRS(instruction);
    int rt = getRT(instruction);
    int immd = getIMMD(instruction);

    printf("issue: %x ", instruction);
    //procura por uma estacao de reserva vazia
    for (int i = 0; i < NUM_RESV_ST; ++i) {
        if (!reservation_station[i].busy) {
            printf(" achou %d ", i);
            if (register_status.status[rs] >= 0) {
                reservation_station[i].qj = register_status.status[rs];
            } else {
                reservation_station[i].vj = register_file.reg[rs];
                reservation_station[i].qj = -1;
            }

            if (register_status.status[rt] >= 0) {
                reservation_station[i].qk = register_status.status[rt];
            } else {
                reservation_station[i].vk = register_file.reg[rt];
                reservation_station[i].qk = -1;
            }

            reservation_station[i].busy = true;
            reservation_station[i].op = op;
            reservation_station[i].A = immd;
            register_status.status[rd] = i;
            fetch();
            break;
        }
    }
}

void execute() {
    for (int i = 0; i < NUM_RESV_ST; ++i) {
        if (reservation_station[i].busy) {
            if (reservation_station[i].qj < 0 && reservation_station[i].qk < 0) {
                resultados[i] = doOperation(i);
            }
        }
    }
}

void write() {
    for (int i = 0; i < NUM_RESV_ST; ++i) {
        if (reservation_station[i].busy && reservation_station[i].qj < 0 &&
                reservation_station[i].qk < 0) {
            for (int j = 0; j < 16; ++j) {
                if (register_status.status[j] == i) {
                    register_file.reg[j] = resultados[i];
                    register_status.status[j] = -1;
                }
            }

            for (int j = 0; j < NUM_RESV_ST; ++j) {
                if (reservation_station[j].qj == i) {
                    reservation_station[j].vj = resultados[i];
                    reservation_station[j].qj = -1;
                }

                if (reservation_station[j].qk == i) {
                    reservation_station[j].vk = resultados[i];
                    reservation_station[j].qk = -1;
                }
            }

            reservation_station[i].busy = false;
        }
    }
}

int sc_main(int argc, char** argv) {
    initMem();
    initReservationStation();
    initRegisterStatus();
    initIQ();
    register_file.reg[0] = 1;
    register_file.reg[1] = 1;
    register_file.reg[2] = 1;
    register_file.reg[3] = 1;

    int i = 0;

    while (/*i < 50*/true) {
        for (int j = 0; j < 16; ++j)
            printf("%d ", register_file.reg[j]);
        printf("\n");
        issue();
        execute();
        write();
        ++i;
    }
    return (EXIT_SUCCESS);
}

